package db

import (
	"encoding/json"
	"errors"
	"time"

	"github.com/etcd-io/bbolt"
)

// 错误：地址为空
var errorPathIsEmpty = errors.New("path should not be empty")

// 错误：数据库尚未打开
var errorDBNotOpened = errors.New("db has not opened, call Open() first")

// 错误：键值不存在
var errorKeyNotExist = errors.New("")

// DB 数据库操作类
type DB struct {
	path   string    // 数据库路径
	boltDB *bbolt.DB // 数据库接口
}

// Open 打开数据库文件
func (db *DB) Open(path string) error {
	// 判空
	if path == "" {
		return errorPathIsEmpty
	}
	// 尝试创建或打开数据库文件
	options := &bbolt.Options{Timeout: 5 * time.Second}
	bolt, err := bbolt.Open(path, 0600, options)
	if err != nil {
		return err
	}
	db.boltDB = bolt
	return nil
}

// Close 关闭数据库
func (db *DB) Close() error {
	if db.boltDB == nil {
		return errorDBNotOpened
	}
	return db.boltDB.Close()
}

// Update 插入或更新一条数据
func (db *DB) Update(table, key []byte, valueObj interface{}) error {
	// 判空
	if db.boltDB == nil {
		return errorDBNotOpened
	}
	// 将valueObj转换成byte数组
	valueBytes, err := json.Marshal(valueObj)
	if err != nil {
		return err
	}
	// 执行更新数据条目事务
	return db.boltDB.Update(func(tx *bbolt.Tx) error {
		bucket := tx.Bucket(table)
		if bucket == nil {
			newBucket, err := tx.CreateBucket(table)
			if err != nil {
				return err
			}
			bucket = newBucket
		}
		return bucket.Put(key, valueBytes)
	})
}

// Delete 删除项目
func (db *DB) Delete(table, key []byte) error {
	// 判空
	if db.boltDB == nil {
		return errorDBNotOpened
	}
	return db.boltDB.Update(func(tx *bbolt.Tx) error {
		bucket := tx.Bucket(table)
		if bucket == nil {
			return nil
		}
		return bucket.Delete(key)
	})
}

// Load 读取一条数据, 返回值true表示读取到数据，false表示未读取到数据，error为错误原因
func (db *DB) Load(table, key []byte, valueObj interface{}) (bool, error) {
	// 判空
	if db.boltDB == nil {
		return false, errorDBNotOpened
	}
	// 执行读取数据条目事务
	err := db.boltDB.View(func(tx *bbolt.Tx) error {
		bucket := tx.Bucket(table)
		if bucket == nil {
			return nil
		}
		value := bucket.Get(key)
		if len(value) == 0 {
			return errorKeyNotExist
		}
		return json.Unmarshal(value, valueObj)
	})
	if err == nil {
		return true, nil
	} else if err == errorKeyNotExist {
		return false, nil
	}
	return false, err
}

// ForEach 遍历全部数据
func (db *DB) ForEach(table []byte, fn func(key, value []byte) error) error {
	// 判空
	if db.boltDB == nil {
		return errorDBNotOpened
	}
	// 执行读取数据条目事务
	return db.boltDB.View(func(tx *bbolt.Tx) error {
		bucket := tx.Bucket(table)
		if bucket == nil {
			return nil
		}
		return bucket.ForEach(fn)
	})
}
